#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace KLib.Camera
{

    public enum CameraFollowMode
    {
        None,
        Instant,
        KeepInBounds,
    }
    public class Camera : ICamera, IPositionable, IRotatable
    {
        #region Fields
        private bool requiresUpdate = true;
        private bool axisAligned = true;

        private Vector2 position;
        private float rotation;
        private Vector2 zoom;
        private string name;

        private Vector2 maxZoom;
        private Vector2 minZoom;

        private CameraFollowMode followMode = CameraFollowMode.None;
        //private bool followMode;
        private Vector2 followTarget;
        private Rectangle followBounds;

        private CameraManager manager;

        private Rectangle viewRegion;
        #endregion

        #region Properties

        /// <summary>
        /// Gets or Sets if the Camera Requires an Update
        /// </summary>
        public bool RequiresUpdate
        {
            get { return requiresUpdate; }
            set { requiresUpdate = value; }
        }


        /// <summary>
        /// Gets or Sets if the Camera is AxisAligned.
        /// 
        /// If True (default), when the camera is moved it behaves as if the axis has not moved (Up is up on the monitor)
        /// If false, when the camera is moved it behaves as if the axis has been rotated with the camera 
        /// (Up is whatever way the top of the sprites is, and moving up may appear to be moving on an angle)
        /// </summary>
        public bool AxisAligned
        {
            get { return axisAligned; }
            set { axisAligned = value; }
        }

        /// <summary>
        /// Gets or Sets the Position of the Camera. To set the Position of the Camera, use Camera.MoveTo() instead.
        /// </summary>
        public Vector2 Position
        {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// Gets or Sets the Position of the Camera. To set the Rotation of the Camera, use Camera.RotateTo() instead.
        /// </summary>
        public float Rotation
        {
            get { return rotation; }
            set { rotation = value; }
        }

        /// <summary>
        /// Gets or Sets the Zoom of the Camera. To Set the ZoomAmount of the Camera, use ZoomTo() instead.
        /// </summary>
        public Vector2 ZoomAmount
        {
            get { return zoom; }
            set { zoom = value; }
        }

        /// <summary>
        /// Gets the Name of the Camera.
        /// </summary>
        public string Name
        {
            get { return name; }
        }

        public Rectangle ViewRegion
        {
            get { return viewRegion; } 
        }
        public Vector2 CenterScreen
        {
            get
            { 
                if (manager != null)
                    return new Vector2(manager.Game.GraphicsDevice.Viewport.Width/2, manager.Game.GraphicsDevice.Viewport.Height/2);
                return Vector2.Zero;
            }
        }

        public CameraManager Manager
        {
            get { return manager; }
            set { manager = value; }
        }
        #endregion

        #region Initialization

        /// <summary>
        /// Creates a new Camera
        /// </summary>
        /// <param name="viewport"></param>
        public Camera()
        {
            position = Vector2.Zero;
            rotation = 0f;
            zoom = Vector2.One;

            maxZoom = Vector2.One * 5;
            minZoom = Vector2.One * 0.1f;
        }

        #endregion

        #region Methods
        /// <summary>
        /// Updates the Camera if it requires an update
        /// </summary>
        public void Update(GameTime gameTime)
        {
            if (!requiresUpdate)
                return;

            viewRegion = new Rectangle((int)position.X, (int)position.Y, manager.Game.GraphicsDevice.Viewport.Width, manager.Game.GraphicsDevice.Viewport.Height);

            if (followMode == CameraFollowMode.KeepInBounds)
            {
                if (!followBounds.Contains((int)followTarget.X,(int)followTarget.Y))
                {
                    Move(Vector2.Normalize(position - followTarget));
                }
            }
            else if (followMode == CameraFollowMode.Instant)
            {
                MoveTo(followTarget - CenterScreen);
            }
        }

        /// <summary>
        /// Updates the Camera even if it does not require an update
        /// </summary>
        public void ForceUpdate(GameTime gameTime)
        {
            bool prvRequiresUpdate = requiresUpdate;
            if (!requiresUpdate)
                requiresUpdate = true;

            Update(gameTime);

            if (!requiresUpdate)
                requiresUpdate = prvRequiresUpdate;
        }

        #region Translation
        /// <summary>
        /// Moves the Camera by a specific distance. Automatically aligns properly if Camera.AxisAligned is true
        /// </summary>
        /// <param name="distance">The distance the camera will move</param>
        public void Move(Vector2 distance)
        {
            if (distance != Vector2.Zero)
            {
                if (axisAligned)
                {

                    if (distance.X != 0)
                    {
                        position.X += (float)Math.Cos(-rotation) * distance.X;
                        position.Y += (float)Math.Sin(-rotation) * distance.X;
                    }

                    if (distance.Y != 0)
                    {
                        position.X += (float)Math.Sin(rotation) * distance.Y;
                        position.Y += (float)Math.Cos(rotation) * distance.Y;
                    }
                }
                else
                {
                    position.X += distance.X;
                    position.Y += distance.Y;
                }
            }

        }

        /// <summary>
        /// Instantly moves the camera to a specified position
        /// </summary>
        /// <param name="position"></param>
        public void MoveTo(Vector2 position)
        {
            this.position = position;
        }

        public Matrix GetCameraMatrix()
        {
            Vector3 rotationOrigin = new Vector3(this.position + CenterScreen, 0);

            return Matrix.CreateTranslation(-rotationOrigin)
                    * Matrix.CreateScale(this.zoom.X,this.zoom.Y,1.0f)
                    * Matrix.CreateRotationZ(this.rotation)
                    * Matrix.CreateTranslation(CenterScreen.X,CenterScreen.Y,0.0f);
        }
        #endregion

        #region Rotation

        /// <summary>
        /// Rotates the Camera by the specified degrees.
        /// </summary>
        /// <param name="degrees"></param>
        public void Rotate(float degrees)
        {
            rotation += MathHelper.ToRadians(degrees);
        }

        /// <summary>
        /// Instantly sets the rotation of the Camera to a specific degree
        /// </summary>
        /// <param name="degrees"></param>
        public void RotateTo(float degrees)
        {
            rotation = degrees;
        }
        #endregion

        #region Zoom
        
        /// <summary>
        /// Zooms the Camera by a specific amount within MinZoom and MaxZoom
        /// </summary>
        /// <param name="amount">The ammount that the camera needs to zoom</param>
        public void Zoom(float amount)
        {
            zoom.X += amount;
            zoom.Y += amount;

            zoom.X = MathHelper.Clamp(zoom.X, minZoom.X, maxZoom.X);
            zoom.Y = MathHelper.Clamp(zoom.Y, minZoom.Y, maxZoom.Y);
        }

        /// <summary>
        /// Zooms the Camera by a specific amount within MinZoom and MaxZoom
        /// </summary>
        /// <param name="amount">The ammount that the camera needs to zoom</param>
        public void Zoom(Vector2 amount)
        {
            zoom += amount;

            zoom = Vector2.Clamp(zoom, minZoom, maxZoom);
        }

        /// <summary>
        /// Instantly Zooms the Camera to a specific point within MinZoom and MaxZoom. 
        /// </summary>
        public void ZoomTo(float zoom)
        {
            this.zoom.X = zoom;
            this.zoom.Y = zoom;
            this.zoom = Vector2.Clamp(this.zoom, minZoom, maxZoom);
        }

        /// <summary>
        /// Instantly Zooms the Camera to a specific point within MinZoom and MaxZoom. 
        /// </summary>
        public void ZoomTo(Vector2 zoom)
        {
            this.zoom = Vector2.Clamp(zoom, minZoom, maxZoom);
        }
        #endregion

        #region Path Following
        //Consider the ability to follow Curves, this would be useful for createing Cut-scences and more
        //cinimatic 'video' type sequences.
        #endregion

        #region Lock-to Objects
        public void Follow(Vector2 position, CameraFollowMode mode)
        {
            this.followMode = mode;
            if (mode == CameraFollowMode.None)
                StopFollowing();
            else
            {
                followTarget = position;
                if (mode == CameraFollowMode.KeepInBounds)
                    followBounds = new Rectangle((int)position.X + (int)(viewRegion.Width * 0.2f), 
                        (int)position.Y + (int)(viewRegion.Height * 0.2f),
                        (int)(viewRegion.Width * 0.8f),
                        (int)(viewRegion.Height * 0.8f));
            }
        }

        public void StopFollowing()
        {
            followTarget = Vector2.Zero;
            followMode = CameraFollowMode.None;
        }
        #endregion

        #endregion

        #region Helper Methods

        #endregion
    }
}
